import Ayaka, { cached, colorful, defineGlobalProperty, isMyRoom, isRoomInterval, log } from '@/ayaka'
import {
    ImportantStructureTypes,
    BuildableStructureTypes,
    WalkableStructureTypes,
    SingleStructureTypes,
    UnbuildableStructureTypes,
    AllStructureTypes,
} from '@/utils/constants'
import { HandleMissingStructCallBack, HandleNewStructCallBack, StructureMap, ZipPos } from './types'
import StructureSite from '../structureSite'
import { RoomSafeState } from '@/room/state/types'

/**
 * 建筑信息缓存
 */
export default class StructureCache {
    /**
     * 处理缺失建筑函数缓存
     */
    public static handleMissingStructCallBacks: HandleMissingStructCallBack[] = []

    /**
     * 处理新增建筑函数缓存
     */
    public static handleNewStructCallBacks: HandleNewStructCallBack[] = []

    /**
     * 无视野房间建筑缓存
     */
    public static darkRoomStructCache: {
        [roomName: string]: {
            time: number
            structures: Structure[]
        }
    } = {}

    /**
     * 阻止实例化
     */
    protected constructor() {
        throw new Error('StructureCache 不可被实例化！')
    }

    /**
     * 获取建筑信息缓存
     */
    public static getMemory(room: Room) {
        if (!room.memory.structures) {
            room.memory.structures = {}
        }
        return room.memory.structures
    }

    /**
     * 将建筑数组转为建筑id为键的对象
     */
    public static structuresToMap<T extends StructureConstant>(structures: ConcreteStructure<T>[]) {
        const structureMap = {} as { [id: string]: ConcreteStructureMap[T] }
        structures.forEach((structure) => (structureMap[structure.id] = structure))
        return structureMap
    }

    /**
     * 更新建筑信息缓存
     * @param interval 更新间隔，默认100，战争时期请自行减小
     */
    public static update(room: MyRoom, interval = 100) {
        if (!isRoomInterval(room, interval)) return

        const structures = StructureCache.getStructures(room)
        const structureGroup = _.groupBy(structures, (structure) => structure.structureType) as StructureMap
        // 清除掉不可建造的建筑
        for (const type of UnbuildableStructureTypes) {
            delete structureGroup[type]
        }

        const memory = StructureCache.getMemory(room)
        // 缺失的重要建筑类型
        const missingStructTypes: BuildableStructureConstant[] = []
        // 新增的建筑类型以及该类型所有建筑的映射
        // 比如新增了1个lab，要重新构建lab系统，此时不仅需要知道新增的那个lab，还需要之前的所有lab，为方便起见，包含所有该类型的建筑
        // const newStructMap: {
        //     [type in BuildableStructureConstant]?: ConcreteStructure<type>[]
        // } = {}

        for (const type of BuildableStructureTypes) {
            // 得到旧的建筑信息
            const oldStructures = memory[type] || {}
            // 得到新的建筑信息
            const newStructures = StructureCache.structuresToMap(structureGroup[type] || [])
            // 是否有重要建筑缺失
            let isImportantStructMissing = false
            // 先看是否有建筑缺少
            Object.keys(oldStructures).forEach((id) => {
                // 建筑存在
                if (newStructures[id]) {
                    delete newStructures[id]
                    return
                }

                // 建筑缺失，先检查该位置是否有该类建筑
                const posTuple = oldStructures[id].split('/').map(Number) as [number, number]
                const pos = new RoomPosition(posTuple[0], posTuple[1], room.name)
                const posStructs = pos.lookFor(LOOK_STRUCTURES)
                const struct = posStructs.find((s) => s.structureType === type)

                if (struct) {
                    // 更新建筑信息
                    oldStructures[struct.id] = `${pos.x}/${pos.y}`
                    delete oldStructures[id]
                    // delete newStructures[struct.id]
                    return
                }

                // 不存在就微妙了，并不一定能直接创建建筑工地
                // 比如我手贱手动拆除了1个 ext，然后又放了1个 wall，这时候虽然 ext 不存在，但是也不用放工地
                // 总结，只有当该位置没有任何其他不可行走的建筑时才放置工地，ram 除外
                if (
                    type !== STRUCTURE_RAMPART &&
                    posStructs.find((s) => !WalkableStructureTypes.includes(s.structureType))
                ) {
                    delete oldStructures[id]
                    return
                }

                // 有敌人存在时不放路的工地
                if (type !== 'road' || room.memory.state.safe?.state === RoomSafeState.normal) {
                    room.createConstructionSite(pos, type)
                }

                if (!isImportantStructMissing && ImportantStructureTypes.includes(type)) {
                    isImportantStructMissing = true
                }
            })

            if (isImportantStructMissing) {
                missingStructTypes.push(type)
            }

            if (!Object.keys(newStructures).length) {
                delete structureGroup[type]
                continue
            }

            // newStructures中剩下的就是新建筑了
            Object.entries(newStructures).forEach(([id, struct]) => {
                if (!memory[type]) memory[type] = {}
                memory[type]![id] = `${struct.pos.x}/${struct.pos.y}`
            })
        }

        // TODO: 如果重要建筑缺失！考虑开启sf，发送邮件，积极防御或者提前跑路等等
        if (missingStructTypes.length) {
            StructureCache.handleMissingStructCallBacks.forEach((callBack) => callBack(room, missingStructTypes))
            // 下一 tick 更新建筑工地缓存
            Ayaka.nextTick(() => StructureSite.update(100, true))
        }

        if (Object.keys(structureGroup).length) {
            log(`检测到新建筑 ${room.name} 类型${JSON.stringify(Object.keys(structureGroup))}`)
            StructureCache.handleNewStructCallBacks.forEach((callBack) => callBack(room, structureGroup))
        }
    }

    /**
     * 获取房间中建筑信息，包括可建造的建筑和固定的建筑
     */
    public static getStructuresByType<T extends StructureConstant>(room: Room, type: T) {
        const key = `_${type}`
        if (Object.prototype.hasOwnProperty.call(room, key)) return room[key as T] as ConcreteStructure<T>[]
        let result: ConcreteStructure<T>[] = []
        // 自己的房间直接走 memory
        if (isMyRoom(room)) {
            const memory = StructureCache.getMemory(room)
            result = Object.keys(memory[type] || {})
                .map((id) => Game.getObjectById(id))
                .filter(Boolean) as ConcreteStructure<T>[]
        } else {
            // 不是自己的房间 find
            const structs = StructureCache.getStructures(room, true)
            result = structs.filter((struct) => struct.structureType === type) as ConcreteStructure<T>[]
        }

        room[key as T] = result as any
        return result
    }

    /**
     * 获取房间中指定类型建筑的位置信息
     */
    public static getPosByType<T extends BuildableStructureConstant>(room: Room, type: T) {
        const memory = StructureCache.getMemory(room)
        return Object.values(memory[type] || {}) as ZipPos[]
    }

    /**
     * 获取房间内所有建筑
     * @param cache 是否使用缓存，默认为 false
     */
    public static getStructures(room: Room, cache = false) {
        if (room.structures) return room.structures
        room.structures = room.find(FIND_STRUCTURES)
        if (cache) {
            // 缓存
            StructureCache.darkRoomStructCache[room.name] = {
                time: Game.time,
                structures: room.structures,
            }
        }
        return room.structures
    }

    /**
     * 从缓存中读取所有建筑
     */
    public static getStructuresFromCache(roomName: string, time = 1000) {
        const cache = StructureCache.darkRoomStructCache[roomName]
        if (!cache) return
        if (Game.time - cache.time > time) {
            delete StructureCache.darkRoomStructCache[roomName]
            return
        }
        return cache.structures
    }

    /**
     * 监视建筑缺失，请传入具名函数，方便缓存
     */
    public static onStructMissing = cached(function (callBack: HandleMissingStructCallBack) {
        StructureCache.handleMissingStructCallBacks.push(callBack)
    })

    /**
     * 监视新建筑出现，请传入具名函数！！！，方便缓存
     */
    public static onNewStruct = cached(function (callBack: HandleNewStructCallBack) {
        StructureCache.handleNewStructCallBacks.push(callBack)
    })

    /**
     * 移除指定 id 的建筑
     */
    public static remove(id: string) {
        const struct = Game.getObjectById(id) as Structure<BuildableStructureConstant>
        if (!struct || !struct.structureType) {
            return colorful(`id为 ${id} 的建筑不存在`, 'red')
        }
        if (!isMyRoom(struct.room)) return colorful(`${struct}不在你的房间内`, 'red')
        const result = struct.destroy()
        if (result === OK) {
            // 清除该建筑在房间中的缓存
            const memory = StructureCache.getMemory(struct.room)
            delete memory[struct.structureType]?.[struct.id]
            return colorful(`${struct} 已移除`, 'green')
        }
        return colorful(`${struct} 移除失败，错误码 ${result}`, 'red')
    }
}

const SingleTypesSet = new Set(SingleStructureTypes)

// 给房间添加建筑类型属性，方便快速获取房间内建筑
for (const type of AllStructureTypes) {
    // 这些建筑 room 上已经有了，不需要
    const defaultStructs = [STRUCTURE_CONTROLLER, STRUCTURE_STORAGE, STRUCTURE_TERMINAL] as StructureConstant[]
    if (defaultStructs.includes(type)) continue

    try {
        Object.defineProperty(Room.prototype, type, {
            get() {
                const result = StructureCache.getStructuresByType(this, type)
                if (SingleTypesSet.has(type)) {
                    return result[0]
                }
                return result
            },
        })
    } catch (error) {
        //
    }
}

defineGlobalProperty('Struct', {
    value: StructureCache,
})
